package com.frank.oj.manager;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.frank.model.entity.contest.Contest;
import com.frank.model.entity.contest.ContestProblem;
import com.frank.model.entity.judge.Judge;
import com.frank.model.entity.problem.Problem;
import com.frank.oj.common.exception.StatusFailException;
import com.frank.oj.common.exception.StatusForbiddenException;
import com.frank.oj.constant.Constants;
import com.frank.oj.crawler.problem.ProblemStrategy;
import com.frank.oj.model.dto.ContestProblemDTO;
import com.frank.oj.model.dto.ProblemDTO;
import com.frank.oj.service.entity.contest.ContestEntityService;
import com.frank.oj.service.entity.contest.ContestProblemEntityService;
import com.frank.oj.service.entity.judge.JudgeEntityService;
import com.frank.oj.service.entity.problem.ProblemEntityService;
import com.frank.oj.shiro.AccountProfile;
import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author frank
 * @Date 2024/4/7
 */
@Component
public class AdminContestProblemManager {
    @Resource
    private ContestProblemEntityService contestProblemEntityService;

    @Resource
    private ProblemEntityService problemEntityService;

    @Resource
    private JudgeEntityService judgeEntityService;

    @Resource
    private RemoteProblemManager remoteProblemManager;

    @Resource
    private ContestEntityService contestEntityService;

    public HashMap<String, Object> getProblemList(Integer limit, Integer currentPage, String keyword, Long cid, Integer problemType, String oj) {
        if (currentPage == null || currentPage < 1) currentPage = 1;
        if (limit == null || limit < 1) limit = 10;
        IPage<Problem> page = new Page<>(currentPage, limit);

        LambdaQueryWrapper<ContestProblem> contestProblemQueryWrapper = new LambdaQueryWrapper<>();
        contestProblemQueryWrapper.eq(ContestProblem::getCid, cid);
        List<ContestProblem> contestProblemList = contestProblemEntityService.list(contestProblemQueryWrapper);

        List<Long> pidList = new LinkedList<>();
        HashMap<Long, ContestProblem> contestProblemHashMap = new HashMap<>();

        contestProblemList.forEach(contestProblem -> {
            contestProblemHashMap.put(contestProblem.getPid(), contestProblem);
            pidList.add(contestProblem.getPid());
        });

        HashMap<String, Object> contestProblem = new HashMap<>();
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();

        if (problemType != null) {
            queryWrapper.eq(Problem::getIsGroup, false) //团队内题目
                    .and(wrapper -> wrapper.eq(Problem::getType, problemType)
                            .or().eq(Problem::getIsRemote, true))
                    .ne(Problem::getAuth, 2); // 1公开，2为私有，3为比赛中 ,不能使用隐藏题
            Contest contest = contestEntityService.getById(cid);
            if (contest.getGid() != null) {
                queryWrapper.ne(Problem::getAuth, 3); // ne为不等于,不能使用正在比赛中的团队内题目
            }
        }

        if (problemType != null) {
            queryWrapper.notIn(!pidList.isEmpty(), Problem::getId, pidList);
        } else {
            queryWrapper.in(!pidList.isEmpty(), Problem::getId, pidList);
        }

        if (oj != null && !"All".equals(oj)) {
            if (!Constants.RemoteOJ.isRemoteOJ(oj)) {
                queryWrapper.eq(Problem::getIsRemote, false);
            } else {
                queryWrapper.eq(Problem::getIsRemote, true).likeRight(Problem::getProblemId, oj);
            }
        }

        if (!StringUtils.isEmpty(keyword)) {
            queryWrapper.and(wrapper -> wrapper.like(Problem::getTitle, keyword).or()
                    .like(Problem::getProblemId, keyword).or()
                    .like(Problem::getAuthor, keyword));
        }
        if (pidList.isEmpty() && problemType == null) {
            queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Problem::getId, null);
        }

        IPage<Problem> problemListPage = problemEntityService.page(page, queryWrapper);

        if (!pidList.isEmpty() && problemType == null) {
            List<Problem> problemList = problemListPage.getRecords();

            List<Problem> sortedProblemList = problemList.stream().sorted(Comparator.comparing(Problem::getId, (a, b) -> {
                ContestProblem x = contestProblemHashMap.get(a);
                ContestProblem y = contestProblemHashMap.get(b);
                if (x == null && y != null) {
                    return 1;
                } else if (x != null && y == null) {
                    return -1;
                } else if (x == null) {
                    return -1;
                } else {
                    return x.getDisplayId().compareTo(y.getDisplayId());
                }
            })).collect(Collectors.toList());
            problemListPage.setRecords(sortedProblemList);
        }
        contestProblem.put("problemList", problemListPage);
        contestProblem.put("contestProblemMap", contestProblemHashMap);

        return contestProblem;
    }

    public Problem getProblem(Long pid) throws StatusForbiddenException, StatusFailException {
        Problem problem = problemEntityService.getById(pid);

        if (problem != null) {
            AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
            boolean isRoot = SecurityUtils.getSubject().hasRole("root");
            boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");

            if (!isRoot && !isProblemAdmin && !userRole.getUsername().equals(problem.getAuthor())) {
                throw new StatusForbiddenException("对不起，你无权限查看题目！");
            }

            return problem;
        } else {
            throw new StatusFailException("题目不存在！");
        }
    }

    public ContestProblem getContestProblem(Long cid, Long pid) throws StatusFailException {
        LambdaQueryWrapper<ContestProblem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ContestProblem::getCid, cid).eq(ContestProblem::getPid, pid);

        ContestProblem contestProblem = contestProblemEntityService.getOne(queryWrapper);
        if (contestProblem == null) {
            throw new StatusFailException("该题目不存在！");
        }
        return contestProblem;

    }

    public void deleteProblem(Long pid, Long cid) throws StatusForbiddenException, StatusFailException {
        if (cid != null) {
            LambdaQueryWrapper<ContestProblem> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ContestProblem::getCid, cid).eq(ContestProblem::getPid, pid);
            contestProblemEntityService.remove(queryWrapper);

            LambdaQueryWrapper<Judge> judgeQueryWrapper = new LambdaQueryWrapper<>();
            judgeQueryWrapper.eq(Judge::getCid, cid).eq(Judge::getPid, pid);
            judgeEntityService.remove(judgeQueryWrapper);

            findContestProblem(pid);

        } else {
            problemEntityService.removeById(pid);
            FileUtil.del(Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + pid);
        }
    }

    /**
     * 查询题目有没有在公共比赛和团队比赛中存在，如果都不存在，则将auth重置为1
     * @param pid
     * @return
     */
    public void findContestProblem(Long pid) throws StatusFailException, StatusForbiddenException {
        //查看公共比赛（团队比赛）是否存在这个题目
        QueryWrapper<ContestProblem> ContestProblemQueryWrapper = new QueryWrapper<>();
        ContestProblemQueryWrapper.eq("pid", pid);
        ContestProblem existedContestProblem = contestProblemEntityService.getOne(ContestProblemQueryWrapper);
        //如果是空，说明没有任何比赛有这个题目，那么应该更新题目的auth=1 (公开状态)
        if(existedContestProblem==null){
            UpdateWrapper<Problem> problemUpdateWrapper = new UpdateWrapper<>();
            problemUpdateWrapper.eq("id", pid)
                    .set("auth",1);
            boolean isOk = problemEntityService.update(problemUpdateWrapper);
            if (!isOk) {
                throw new StatusFailException("更新失败");
            }
        }
    }

    public Map<Object, Object> addProblem(ProblemDTO problemDto) throws StatusFailException {
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getProblemId, problemDto.getProblem().getProblemId().toUpperCase());
        Problem problem = problemEntityService.getOne(queryWrapper);
        if (problem != null) {
            throw new StatusFailException("该题目ID已存在，请更换重试！");
        }
        problemDto.getProblem().setAuth(3); // 在比赛列表创建的题目默认为比赛中
        boolean isOk = problemEntityService.adminAddProblem(problemDto);
        if (isOk) {
            return MapUtil.builder().put("pid", problemDto.getProblem().getId()).map();
        } else {
            throw new StatusFailException("添加题目失败！（比赛）");
        }
    }

    public void updateProblem(ProblemDTO problemDto) throws StatusFailException, StatusForbiddenException {
        AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        boolean isProblemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");

        if (!isRoot && !isProblemAdmin && !userRole.getUsername().equals(problemDto.getProblem().getAuthor())) {
            throw new StatusForbiddenException("对不起，你无权限修改题目！");
        }

        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getProblemId, problemDto.getProblem().getProblemId().toUpperCase());
        Problem problem = problemEntityService.getOne(queryWrapper);

        if (problem != null && !problem.getId().equals(problemDto.getProblem().getId())) {
            throw new StatusFailException("该题目ID已存在，请更换重试！");
        }

        problemDto.getProblem().setModifiedUser(userRole.getUsername());

        boolean isOk = problemEntityService.adminUpdateProblem(problemDto);
        if (!isOk) {
            throw new StatusFailException("修改题目失败！");
        }
    }

    public ContestProblem setContestProblem(ContestProblem contestProblem) throws StatusFailException {

        boolean isOk = contestProblemEntityService.saveOrUpdate(contestProblem);
        if (isOk){
            contestProblemEntityService.syncContestRecord(contestProblem.getPid(), contestProblem.getCid(), contestProblem.getDisplayId());
            return contestProblem;
        }else {
            throw new StatusFailException("更新题目失败！");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void addProblemFromPublic(ContestProblemDTO contestProblemDto) throws StatusFailException {
        Long cid = contestProblemDto.getCid();
        Long pid = contestProblemDto.getPid(); // 题目id
        String displayId = contestProblemDto.getDisplayId(); // 题目在比赛中的展示id

        LambdaQueryWrapper<ContestProblem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ContestProblem::getCid,cid)
                .and(wrapper->wrapper.eq(ContestProblem::getPid,pid).or()
                        .eq(ContestProblem::getDisplayId,displayId));
        ContestProblem contestProblem = contestProblemEntityService.getOne(queryWrapper);
        if (contestProblem!=null){
            throw new StatusFailException("该题目已存在！");
        }

        Problem problem = problemEntityService.getById(pid);
        String displayName = problem.getTitle();

        boolean updated = problemEntityService.saveOrUpdate(problem.setAuth(3));
        boolean isOk = contestProblemEntityService.saveOrUpdate(new ContestProblem()
                .setCid(cid).setPid(pid).setDisplayTitle(displayName).setDisplayId(displayId));

        if (!updated || !isOk){
            throw new StatusFailException("添加题目失败！");
        }
    }

    public void importContestRemoteOJProblem(String name, String problemId, Long cid, String displayId) throws StatusFailException {
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Problem::getProblemId,name.toUpperCase()+"-"+problemId);
        Problem problem = problemEntityService.getOne(queryWrapper,false);

        if (problem==null){
            AccountProfile userRole = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
            try {
                ProblemStrategy.RemoteProblemInfo otherOJProblemInfo = remoteProblemManager.getOtherOJProblemInfo(name.toUpperCase(), problemId, userRole.getUsername());

                if (otherOJProblemInfo!=null){
                    problem = remoteProblemManager.adminAddOtherOJProblem(otherOJProblemInfo, name);
                    if (problem==null){
                        throw new StatusFailException("导入远程题目【"+name+"-"+problemId+"】失败，请重试！");
                    }
                }else {
                    throw new StatusFailException("导入远程题目【"+name+"-"+problemId+"】失败！原因：可能是与该OJ链接超时或题号格式错误,请重试！");
                }
            } catch (Exception e) {
                throw new StatusFailException(e.getMessage());
            }
        }

        LambdaQueryWrapper<ContestProblem> contestProblemQueryWrapper = new LambdaQueryWrapper<>();
        Problem finalProblem = problem;
        contestProblemQueryWrapper.eq(ContestProblem::getCid, cid)
                .and(wrapper -> wrapper.eq(ContestProblem::getPid, finalProblem.getId())
                        .or()
                        .eq(ContestProblem::getDisplayId, displayId));

        ContestProblem contestProblem = contestProblemEntityService.getOne(contestProblemQueryWrapper);
        if (contestProblem!=null){
            throw new StatusFailException("导入远程题目【"+name+"-"+problemId+"】失败，该题目已存在！");
        }

        String title = problem.getTitle();
        boolean updateProblem = problemEntityService.saveOrUpdate(problem.setAuth(3));

        boolean isOk = contestProblemEntityService.saveOrUpdate(new ContestProblem().setCid(cid).setPid(problem.getId()).setDisplayId(displayId).setDisplayTitle(title));

        if (!updateProblem || !isOk){
            throw new StatusFailException("导入远程题目【"+name+"-"+problemId+"】失败，请重试！");
        }
    }
}
